﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using Microsoft.VisualBasic;
using UMC.Data;

namespace UMC.Net
{
    /// <summary>
    /// 功能与HttpWebResponse一样，网络请求只是用
    /// </summary>

    public class NetHttpResponse : Mime, IDisposable
    {
        public NetHttpRequest Request => webRequest;

        NetHttpRequest webRequest;

        public StringValue ProtocolVersion
        {
            get
            {
                return Method(this.Frist);//.Span.UTF8();
            }
        }
        public bool IsHead => _isHead;
        bool _isHead;

        static void Http(NetContext context, NetHttpRequest webRequest, Action<NetHttpResponse> prepare)
        {
            NetHttpResponse httpResponse = new NetHttpResponse(prepare, webRequest.Memory)
            {
                webRequest = webRequest,
                _isHead = webRequest.Method == HttpMethod.HEAD
            };
            if (context.ContentLength > 0)
            {
                NetProxy.Instance(webRequest, httpResponse, (tcp) =>
                 {
                     context.ReadAsData((b, i, c) =>
                     {
                         if (b.Length == 0 && c == 0)
                         {
                             if (i == -1)
                             {
                                 tcp.Dispose();
                                 httpResponse.ReceiveException(new System.Net.Sockets.SocketException(104));
                             }
                         }
                         else
                         {
                             tcp.Body(b, i, c);
                         }
                     });

                 }, httpResponse.ReceiveException);
            }
            else
            {
                NetProxy.Instance(webRequest, httpResponse, (tcp) => { }, httpResponse.ReceiveException);
            }

        }
        static void Http(Stream input, NetHttpRequest webRequest, Action<NetHttpResponse> prepare)
        {
            NetHttpResponse httpResponse = new NetHttpResponse(prepare, webRequest.Memory)
            {
                webRequest = webRequest,
                _isHead = webRequest.Method == HttpMethod.HEAD//.SequenceEqual
            };

            NetProxy.Instance(webRequest, httpResponse
            , (tcp) =>
            {
                if (input != null)
                {
                    var header = System.Buffers.ArrayPool<byte>.Shared.Rent(0x200);
                    try
                    {
                        int i = input.Read(header, 0, header.Length);
                        while (i > 0)
                        {
                            tcp.Body(header, 0, i);
                            i = input.Read(header, 0, header.Length);
                        }
                        input.Close();
                        input.Dispose();
                    }
                    finally
                    {
                        System.Buffers.ArrayPool<byte>.Shared.Return(header);
                    }
                }

            }, httpResponse.ReceiveException);
        }

        static HeadValue keepalive = new HeadValue("Connection", "keep-alive");
        static void CheckHeader(NetHttpRequest webRequest)
        {
            webRequest.Add(keepalive);
        }
        Exception _error;

        protected override void ReceiveError(Exception ex)
        {
            if (ex == null)
            {
                _error = new System.Net.Sockets.SocketException(104);
            }
            else
            {
                _error = ex;
            }
            if (this.Prepare != null)
            {
                m_StatusCode = HttpStatusCode.BadGateway;
                this.Headers.Add(new HeadValue(StringValue.ContentType, StringValue.ContentTypeText));

                var errStr = $"502 BadGateway\r\n\r\n{_error?.Message}";
                UMC.Data.Utility.Error("HTTP", DateTime.Now, $"{this.webRequest.Address}", $"{this.webRequest.Headers}", _error?.ToString());
                var by = System.Buffers.ArrayPool<byte>.Shared.Rent(System.Text.ASCIIEncoding.UTF8.GetByteCount(errStr));

                var size = System.Text.ASCIIEncoding.UTF8.GetBytes(errStr, 0, errStr.Length, by, 0);
                this.ContentLength = size;

                try
                {
                    this.Prepare(this);
                    this.Body(by, 0, size);
                    this.Finish();
                }
                catch (Exception ex3)
                {
                    UMC.Data.Utility.Error("HTTP", this.webRequest.RawUrl, this.webRequest.Address.AbsoluteUri, ex3.ToString());
                }
                finally
                {
                    this.Prepare = null;
                    System.Buffers.ArrayPool<byte>.Shared.Return(by);
                }

            }
            else
            {
                _readData?.Invoke(Array.Empty<byte>(), -1, 0);
            }
        }

        public static void Header(NetHttpRequest request, NetWriteData writer)
        {
            var webHeader = request.Headers.Span;
            Method(request.Method).Output(writer);
            writer(Mime.HeaderSplit, 1, 1);
            request.RawUrl.Output(writer);
            writer(Mime.HeaderSplit, 1, 1);

            writer(Mime.HTTP11, 0, 8);
            // Method(HttpMethod.HTTP11).Output(writer);

            writer(Mime.RowEnd, 0, 2);
            foreach (var item in webHeader)
            {
                item.Name.Output(writer);
                writer(Mime.HeaderSplit, 0, 2);
                item.Value.Output(writer);
                writer(Mime.RowEnd, 0, 2);
            }
            writer(Mime.RowEnd, 0, 2);
        }
        internal static void Create(NetHttpRequest webRequest, HttpMethod method, Action<NetHttpResponse> prepare)
        {
            CheckHeader(webRequest);
            webRequest.Method = method;
            NetHttpResponse httpResponse = new NetHttpResponse(prepare, webRequest.Memory)
            {
                webRequest = webRequest,
                _isHead = method == HttpMethod.HEAD
            };
            NetProxy.Instance(webRequest, httpResponse, (tcp) => { }, httpResponse.ReceiveException);
        }
        internal static void Create(NetHttpRequest webRequest, HttpMethod method, string context, Action<NetHttpResponse> prepare)
        {
            CheckHeader(webRequest);
            webRequest.Method = method;
            NetHttpResponse httpResponse = new NetHttpResponse(prepare, webRequest.Memory)
            {
                webRequest = webRequest
            };
            if (String.IsNullOrEmpty(context))
            {
                NetProxy.Instance(webRequest, httpResponse, (tcp) => { }, httpResponse.ReceiveException);
            }
            else
            {
                webRequest.ContentLength = Encoding.UTF8.GetByteCount(context);

                NetProxy.Instance(webRequest, httpResponse, (tcp) =>
                {

                    var header = System.Buffers.ArrayPool<byte>.Shared.Rent(0x200);
                    try
                    {
                        int i = 0, bufferSize = 0;

                        while (i < context.Length)
                        {
                            if (bufferSize + 6 > header.Length)
                            {
                                tcp.Body(header, 0, bufferSize);
                                bufferSize = 0;
                            }

                            bufferSize += System.Text.Encoding.UTF8.GetBytes(context, i, 1, header, bufferSize);
                            i++;
                        }
                        if (bufferSize > 0)
                        {
                            tcp.Body(header, 0, bufferSize);
                        }
                    }
                    finally
                    {
                        System.Buffers.ArrayPool<byte>.Shared.Return(header);
                    }
                }, httpResponse.ReceiveException);
            }

        }
        internal static void Create(NetHttpRequest webRequest, HttpMethod method, System.IO.Stream context, Action<NetHttpResponse> prepare)
        {
            CheckHeader(webRequest);
            webRequest.Method = method;
            webRequest.ContentLength = context.Length;
            Http(context, webRequest, prepare);

        }
        internal static void Create(NetHttpRequest webRequest, NetContext context, Action<NetHttpResponse> prepare)
        {

            CheckHeader(webRequest);
            if (context.ContentLength >= 0)
            {
                webRequest.ContentLength = context.ContentLength.Value;
            }
            webRequest.Method = context.Method;
            Http(context, webRequest, prepare);

        }
        internal static void Create(NetHttpRequest webRequest, HttpMethod method, System.IO.Stream context, long contentLength, Action<NetHttpResponse> prepare)
        {

            CheckHeader(webRequest);
            if (contentLength >= 0)
            {
                webRequest.ContentLength = contentLength;
            }
            webRequest.Method = method;

            Http(context, webRequest, prepare);

        }
        private NetHttpResponse(Action<NetHttpResponse> prepare, ByteChunk memory) : base(memory)
        {
            this.Prepare = prepare;
        }

        HttpStatusCode? m_StatusCode;
        public HttpStatusCode StatusCode
        {
            get
            {
                if (m_StatusCode.HasValue == false)
                {
                    if (UMC.Data.Utility.TryParse(this.Second.Span, 10, out int value))
                    {
                        this.m_StatusCode = (HttpStatusCode)value;
                    }
                    else
                    {
                        this.m_StatusCode = HttpStatusCode.Continue;
                    }
                }
                return this.m_StatusCode.Value;
            }
        }
        public String StatusDescription
        {
            get
            {
                return this.Third.Span.UTF8();
            }
        }
        protected override void Headed()
        {
            if (_isHead)
            {
                this.ContentLength = 0;
            }
            try
            {
                Prepare(this);
            }
            catch (Exception ex)
            {
                this.ReceiveException(ex);
            }
            finally
            {
                Prepare = null;
            }
        }

        Action<NetHttpResponse> Prepare;
        public bool IsReadBody
        {
            get;
            private set;
        }
        public override void Finish()
        {
            _readData?.Invoke(Array.Empty<byte>(), 0, 0);
            this.IsReadBody = true;
        }

        NetWriteData _readData;
        protected override void Body(byte[] data, int offset, int size)
        {
            if (size > 0)
            {
                _readData?.Invoke(data, offset, size);
            }
        }
        public void ReadAsStream(Action<System.IO.Stream> action, Action<Exception> error)
        {
            var ms = new TempStream();

            this.ReadAsData((b, i, c) =>
            {
                if (c == 0 && b.Length == 0)
                {
                    if (i == -1)
                    {
                        ms.Close();
                        ms.Dispose();
                        error(_error);
                    }
                    else
                    {
                        ms.Flush();
                        ms.Position = 0;
                        try
                        {
                            action(ms);
                        }
                        catch (Exception ex)
                        {
                            ms.Close();
                            ms.Dispose();
                            error(ex);
                        }
                    }
                }
                else
                {
                    ms.Write(b, i, c);
                }
            });


        }
        public Exception Error => _error;
        public void ReadAsData(Net.NetWriteData readData)
        {

            if (this._isHead)
            {
                readData(Array.Empty<byte>(), 0, 0);
            }
            else
            {
                if (IsReadBody)
                {
                    readData(Array.Empty<byte>(), this.IsHttpFormatError ? -1 : 0, 0);
                }
                else
                {
                    this._readData = readData;
                }
            }
        }
        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
            GC.SuppressFinalize(this);
        }
    }


}
